﻿#region License
/*
Copyright (c) 2009, Open University of the Netherlands
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided 
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the 
  following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of Open University of the Netherlands nor the names of its contributors may be 
  used to endorse or promote products derived from this software without specific prior written 
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*/
#endregion

namespace EwbDesigner.NET
{
    using System;
    using System.Collections;
    using XmlTreeNode = System.Collections.Generic.SimpleTreeNode<EwbDesigner.NET.XmlCacheItem>;
    using XsdTreeNode = System.Collections.Generic.SimpleTreeNode<EwbDesigner.NET.XsdCacheItem>;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Windows.Forms;
    using System.Xml;
    using System.Xml.Schema;
    using System.Globalization;

    using BrightIdeasSoftware;

    using EwbDesigner.NET.Classes;

    using WeifenLuo.WinFormsUI.Docking;
    using System.Collections.Generic;
    using System.Collections.Specialized;

    public partial class StructureForm : DockContent
    {
        #region Fields

        static readonly StructureForm _instance = new StructureForm();

        private static String ListSeparator = CultureInfo.CurrentCulture.TextInfo.ListSeparator;

        private string fModel;

        #endregion Fields

        #region Constructors

        private StructureForm()
        {
            InitializeComponent();

            HideOnClose = true;
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Collection wraps the Objects of the ObjectListView.
        /// </summary>
        public static System.Collections.IEnumerable Collection
        {
            //get
            //{
            //    return Structure.GetObjects();
            //}

            set
            {
                Structure.ClearObjects();// SetObjects(null);
                Structure.SetObjects(value);
            }
        }

        /// <summary>
        /// Singleton Instance.
        /// Visible when reflecting.
        /// </summary>
        public static StructureForm Instance
        {
            get { return _instance; }
        }

        public static TreeListView Structure
        {
            get { return Instance.treeListView1; }
        }

        /// <summary>
        /// The Model used for displaying in the ObjectListView.
        /// </summary>
        public String Model
        {
            get
            {
                return fModel;
            }
            set
            {
                ProcessModel(value);
            }
        }

        #endregion Properties

        #region Event Handlers

        /// <summary>
        /// Handle Clicking the Add MenuItem'key SubItems.
        /// Add a TreeNode and it'key mandatory offspring.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void DoAddXmlTreeNode(object sender, EventArgs e)
        {
            DoAddXmlTreeNode((String)((ToolStripMenuItem)sender).Tag);
        }

        /// <summary>
        /// Callback Helper for the XSD parsing/compilation.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="args">-</param>
        public static void OnValidationCallback(object sender, ValidationEventArgs args)
        {
            if (args.Severity == XmlSeverityType.Warning)
            {
                Trace.Fail("Warning", args.Message);
            }
            else if (args.Severity == XmlSeverityType.Error)
            {
                Debug.Fail("Error", args.Message);
            }
        }

        private void StructureForm_DockStateChanged(object sender, EventArgs e)
        {
            if (DockState == DockState.Unknown)
            {
                Blank();
            }
        }

        private void TreeForm_Load(object sender, EventArgs e)
        {
            /////////////////////////
#if DEBUG
            if (this.MdiParent != null)
            {
                Debug.WriteLine("MdiParent Present");
            }
            else
            {
                Debug.WriteLine("MdiParent Missing");
            }
#endif
            //Structure.CheckBoxes = false;
            //Structure.DragSource = new SimpleDragSource();
            //Structure.DropSink = new RearrangingDropSink(false);

            ModelDelegates();
        }

        /// <summary>
        /// Examine the selected Node of the SourceControl (a TreeView).
        /// Show the approriate Add MenuItem'key SubMenuItems. 
        /// Enable or Disable the Remove MenuItem.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
        {
            if (typeof(TreeListView).IsInstanceOfType(((ContextMenuStrip)sender).SourceControl))
            {
                TreeListView tv = (TreeListView)((ContextMenuStrip)sender).SourceControl;
                XmlTreeNode tn = (XmlTreeNode)tv.GetSelectedObject();

                if (tn != null)
                {
                    Debug.WriteLine("+" + tn.Path, "Trace");

                    String path = tn.Path;

                    addToolStripMenuItem.Enabled = false;

                    foreach (ToolStripItem tsi in addToolStripMenuItem.DropDownItems)
                    {
                        tsi.Visible = false;
                    }

                    //Count sectionnumber of nodes already present...
                    Int32 scnt = 0;
                    if (tn.Parent != null)
                    {
                        foreach (XmlTreeNode xmlnode in tn.Parent.Children)
                        {
                            if (xmlnode.Path == path) //Search for other ourselves!
                            {
                                scnt++;
                            }
                        }
                    }

                    //Walk All Nodes and see if they are allowed under the current selected node.
                    foreach (XsdTreeNode xsdnode in Data.XsdModel)
                    {
                        if (xsdnode.IsChildOf(path))
                        {
                            Int32 cnt = 0;

                            //Check if present and check if it'key multiplicity.
                            foreach (XmlTreeNode t in tn.Children)
                            {
                                if (t.Path == xsdnode.Path)
                                {
                                    cnt++;
                                }
                            }

                            if (cnt == 0) //!ContainsKey...
                            {
                                //The node to be added is allowed but not present...

                                foreach (ToolStripItem tsi in addToolStripMenuItem.DropDownItems)
                                {
                                    if ((String)tsi.Tag == xsdnode.Path)
                                    {
                                        //Determine wether to show the menuitem...
                                        Boolean show = true;
                                        if (xsdnode.Value.ischoice)
                                        {
                                            show = tn.Children.Count < xsdnode.Value.maxchoice;
                                        }

                                        if (show)
                                        {
                                            //Note, tsi.Visible will remain false if the Parent MenuItem is invisible.
                                            addToolStripMenuItem.Enabled = true;
                                            tsi.Visible = true;
                                        }
                                        Console.WriteLine("Enabled {0}", xsdnode.Path);
                                    }
                                }
                            }
                            else
                            {
                                //The node to be added is allowed and already present, 
                                //so do more checks on multiplicity...

                                XsdCacheItem val1 = Data.XsdModel.NodeByPath(xsdnode.Path).Value;
                                if (val1 != null)
                                {
                                    Console.WriteLine("Found {0} {1} times", xsdnode.Path, cnt.ToString());
                                    if (val1.max > cnt)
                                    {
                                        foreach (ToolStripItem tsi in addToolStripMenuItem.DropDownItems)
                                        {
                                            if ((String)tsi.Tag == xsdnode.Path)
                                            {
                                                //Determine wether to show the menuitem...
                                                Boolean show = true;
                                                if (xsdnode.Value.ischoice)
                                                {
                                                    show = tn.Children.Count < xsdnode.Value.maxchoice;
                                                }

                                                if (show)
                                                {
                                                    //Note, tsi.Visible will remain false if the Parent MenuItem is invisible.
                                                    addToolStripMenuItem.Enabled = true;
                                                    tsi.Visible = true;
                                                }
                                                Console.WriteLine("Enabled {0}", xsdnode.Path);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    //Enable MoveUp MenuItem
                    omhoogToolStripMenuItem.Enabled = false;
                    {
                        XmlTreeNode next = tn.Prev();

                        if (next != null)
                        {
                            //Note: We check for Choice (different elements) and same nodes.
                            XsdCacheItem xci = Data.XsdModel.NodeByPath(next.Path).Value;
                            omhoogToolStripMenuItem.Enabled = (next != null && (xci.ischoice || tn.Value.name == xci.name));
                        }
                    }

                    //Enable MoveDown MenuItem
                    omlaagToolStripMenuItem.Enabled = false;
                    {
                        XmlTreeNode next = tn.Next();

                        if (next != null)
                        {
                            //Note: We check for Choice (different elements) and same nodes.
                            XsdCacheItem xci = Data.XsdModel.NodeByPath(next.Path).Value;
                            omlaagToolStripMenuItem.Enabled = (next != null && (xci.ischoice || tn.Value.name == xci.name));
                        }
                    }

                    //Enable Remove MenuItem

                    {   //Fetch the XsdCacheItem of the node itself.
                        XsdCacheItem xci = Data.XsdModel.NodeByPath(path).Value;

                        //Check for isUnbounded and allow removal when there is more than one node.

                        //Check multiplicity (either min 0 or Unbounded and more then one present).
                        //Beware NOT to use cnt here as that's the count of a particular childnode.
                        if (xci != null)
                        {
                            remToolStripMenuItem.Enabled = (xci.min < scnt);
                        }
                        else
                        {
                            remToolStripMenuItem.Enabled = false;
                        }
                    }
                }
                else
                {
                    addToolStripMenuItem.Enabled = (tn != null);
                    remToolStripMenuItem.Enabled = (tn != null);

                    omhoogToolStripMenuItem.Enabled = false;
                    omlaagToolStripMenuItem.Enabled = false;
                }
            }
        }

        /// <summary>
        /// Remove the Selected TreeNode from the TreeView.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        private void remToolStripMenuItem_Click(object sender, EventArgs e)
        {
            XmlTreeNode xtn = (XmlTreeNode)Structure.SelectedObject;
            if (xtn != null)
            {
                DoDeleteTreeNodes(xtn);
            }
        }

        private void treeListView1_SelectedIndexChanged(object sender, EventArgs e)
        {
            XmlTreeNode editing = (XmlTreeNode)((TreeListView)sender).GetSelectedObject();

            if (editing != null)
            {
                Console.WriteLine("Selected {0}", editing.Path);

                PropertiesForm.Instance.CreateEditor(editing);

                XsdTreeNode xsdnode = Data.XsdModel.NodeByPath(editing.Path);
                if (xsdnode != null)
                {
                    Program.MainForm.StatusBarText("{0} - occurs({1}..{2}) - path='{3}' - Choice={4} - Id={5}", xsdnode.Value.name, xsdnode.Value.min, (xsdnode.Value.max == decimal.MaxValue) ? "∞" : xsdnode.Value.max.ToString(), xsdnode.Path, xsdnode.Value.ischoice.ToString(), editing.Id);
                }
                else
                {
                    Program.MainForm.StatusBarText("**error**");
                }
            }
            else
            {
                PropertiesForm.Instance.RemoveEditor();

                Program.MainForm.StatusBarText("");
            }
        }

        private void treeListView1_CellEditFinishing(object sender, CellEditEventArgs e)
        {
            if ((e.Column.Index == 0) && (e.RowObject is XmlTreeNode))
            {
                XmlTreeNode xtn = ((XmlTreeNode)(e.RowObject));
                XsdTreeNode xsd = Data.XsdModel.NodeByPath(xtn.Path);

                //if (Data.editing != null)
                if (xsd.Value.attributes.ContainsKey("titel"))
                {
                    //Debug.WriteLine(s.GetType().Name, "Trace");
                    //Debug.WriteLine(e.ChangedItem.Label, "Trace");

                    if (xsd != null && xsd.Value.attributes["titel"].IsFlagSet(XsdAttributeFlags.TrimBlanks))
                    {
                        xtn.Value.attributes["titel"].value = e.Control.Text.Trim();
                    }
                    else
                    {
                        xtn.Value.attributes["titel"].value = e.Control.Text.ToString();
                    }

                    StructureForm.Structure.RefreshObject(StructureForm.Structure.SelectedObject);
                }
            }
        }

        private void omhoogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            XmlTreeNode xml1 = (XmlTreeNode)Structure.GetSelectedObject();
            XmlTreeNode xml2 = xml1.Prev();

            if (xml1 != null && xml2 != null && xml1.Parent == xml2.Parent)
            {
                XmlTreeNode par = xml1.Parent;

                par.Children.Swap(xml1.ChildIndex, xml2.ChildIndex);

                //Structure.BuildList(true);
                Structure.RefreshObject(par);
            }
        }

        private void omlaagToolStripMenuItem_Click(object sender, EventArgs e)
        {
            XmlTreeNode xml1 = (XmlTreeNode)Structure.GetSelectedObject();
            XmlTreeNode xml2 = xml1.Next();

            if (xml1 != null && xml2 != null && xml1.Parent == xml2.Parent)
            {
                XmlTreeNode par = xml1.Parent;

                par.Children.Swap(xml1.ChildIndex, xml2.ChildIndex);

                //Structure.BuildList(true);
                Structure.RefreshObject(par);
            }
        }

        #endregion Event Handlers

        #region Methods

        public void ClearnPopupMenu()
        {
            addToolStripMenuItem.DropDownItems.Clear();
        }

        public void DoAddMenuItem(XsdTreeNode xsdnode)
        {
            Int32 ndx = addToolStripMenuItem.DropDownItems.Add(new ToolStripMenuItem(xsdnode.Value.name, null, null, xsdnode.Value.name));
            addToolStripMenuItem.DropDownItems[ndx].Tag = xsdnode.Path;
            addToolStripMenuItem.DropDownItems[ndx].Click += new EventHandler(DoAddXmlTreeNode);
        }

        /// <summary>
        /// Handle Clicking the Add MenuItem'key SubItems.
        /// Add a TreeNode and it'key mandatory offspring.
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">-</param>
        public void DoAddXmlTreeNode(String path)
        {
            XmlTreeNode xmlnode = (XmlTreeNode)StructureForm.Structure.GetSelectedObject();
            XsdTreeNode xsdnode = Data.XsdModel.NodeByPath(path);

            if (xmlnode != null && xsdnode != null)
            {
                DoAddXmlModelNodes(xsdnode, xmlnode);

                Structure.RefreshObject(xmlnode);

                //Trace.WriteLine("Clicked '" + (String)((ToolStripMenuItem)sender).Tag + "'", "Trace");
            }
        }

        private void Blank()
        {
            //if (!Structure.IsDisposed)
            //{
            //    Structure.SetObjects(null);
            //}
        }

        /// <summary>
        /// Add a Node to the Tree and add its mandatory offspring by recursing.
        /// </summary>
        /// <param name="childNode">The Node to add.</param>
        /// <param name="parentNode">The TreeNode to add nodes too (or null for a Root Node).</param>
        private void DoAddXmlModelNodes(XsdTreeNode childNode, XmlTreeNode parentNode)
        {

            Int32 ip = -1;

            //Add node either as Root or as Child and at the corract place....
            XmlTreeNode NewNode = null;

            //To find the insertion point we walk the children of the parentNode where the NewNode 
            //is to be generated and added based on the XsdTreeNode childNode.
            //
            //What we do is simply walk until the childNode's ChildIndex passes the XsdTreeeNode belonging
            //to the child of the parentNode. That way we stop either at the insertion point of a new node or 
            //skip all already present nodes of the same type. We also keep skipping choice elements.
            //
            //Special point is if there is no parentNode in which case we just add the node as Root node.

            if (parentNode != null)
            {
                foreach (XmlTreeNode child in parentNode.Children)
                {
                    //We will always encounter the element itself in the xsd

                    //Find xsd node of xml instance
                    XsdTreeNode xsd = Data.XsdModel.NodeByPath(child.Path);

                    //Loop until we have passed the child's xsd.
                    if (xsd.ChildIndex > childNode.ChildIndex)
                    {
                        //Skip already present choice items too...

                        //TODO Check if this is correct as we may have stumbled upon an adjacent choice..

                        if (!childNode.Value.ischoice || (xsd.Value.ischoice != childNode.Value.ischoice))
                        {
                            break;
                        }
                    }

                    ip = child.ChildIndex;
                }

                //Extra safeguard if it's the last node, we add istead of insert.
                if (ip > parentNode.Children.Count || ip < 0)
                {
                    NewNode = (XmlTreeNode)(parentNode.Children).Add(new XmlCacheItem(childNode.Value.name, new XmlAttributes(childNode.Value.attributes)));
                }
                else
                {
                    NewNode = (XmlTreeNode)(parentNode.Children).Insert(ip + 1, new XmlCacheItem(childNode.Value.name, new XmlAttributes(childNode.Value.attributes)));
                }

            }
            else
            {
                //Insert a single Node at correct location...
                Data.XmlModel.Root.Value = new XmlCacheItem(childNode.Value.name, new XmlAttributes(childNode.Value.attributes));
                NewNode = Data.XmlModel.Root;
            }

            //Recurse this nodes 

            foreach (XsdTreeNode xsdnode in childNode.Children)
            {
                //For all children of the node added...
                if (xsdnode.IsChildOf(childNode.Path))
                {
                    for (Int32 i = 0; i < xsdnode.Value.min; i++)
                    {
                        DoAddXmlModelNodes(xsdnode, NewNode);
                    }
                }
            }
            //}
        }

        /// <summary>
        /// Remove a Node and its offspring from the TreeView.
        /// </summary>
        /// <param name="tn">The TreeNode to remove.</param>
        private void DoDeleteTreeNodes(XmlTreeNode xtn)
        {
            if (xtn.Parent != null)
            {
                XmlTreeNode p = (XmlTreeNode)xtn.Parent;

                p.Children.Remove(xtn);

                StructureForm.Structure.RefreshObject(p);
            }
            else
            {
                Data.XmlModel.Root.Value = null;

                StructureForm.Structure.ClearObjects();
            }
        }

        /// <summary>
        /// Parse a XSD Particle recursivly (ComplexType or Element).
        /// </summary>
        /// <param name="particle">The XSD Particle to Parse.</param>
        /// <param name="path">The base path of the Particle.</param>
        void DoTraverseParticle(XmlSchemaParticle particle, string path, XsdTreeNode node)
        {
            String Annotation = null;

            Debug.Indent();

            //Dump Annotation...
            //TODO: Keep it for the next Node!
            if (particle.Annotation != null)
            {
                foreach (XmlSchemaObject xso in particle.Annotation.Items)
                {
                    if (xso is XmlSchemaDocumentation)
                    {
                        foreach (XmlNode xn in ((XmlSchemaDocumentation)xso).Markup)
                        {
                            //Debug.WriteLine(xn.InnerText);

                            Annotation += xn.InnerText;
                        }
                    }
                }
            }

            //if (particle is XmlSchemaChoice)
            //{
            //    Debug.Print("***Encountered Choice***");
            //}

            //if (particle is XmlSchemaSequence)
            //{
            //    Debug.Print("***Encountered Sequence***");
            //}

            if (particle is XmlSchemaElement)
            {
                //xs:Element
                XmlSchemaElement elem = particle as XmlSchemaElement;
                //Debug.Print("Encountered: {0} - {1}", elem.Name, elem.SchemaTypeName.Name);

                XmlSchemaType type = (XmlSchemaType)elem.ElementSchemaType;
                XmlSchemaComplexType complexType = type as XmlSchemaComplexType;

                XsdAttributes attributes = new XsdAttributes();

                //Map Attributes (Xsd)Type onto C# Types supported by the PropertyGrid.

                if (complexType != null && complexType.AttributeUses.Count > 0)
                {
                    IDictionaryEnumerator enumerator = complexType.AttributeUses.GetEnumerator();

                    while (enumerator.MoveNext())
                    {
                        XmlSchemaAttribute attribute = (XmlSchemaAttribute)enumerator.Value;

                        XsdAttributeFlags flags = XsdAttributeFlags.None;
                        XsdAttributeEditor editor = XsdAttributeEditor.Auto;

                        StringCollection values = new StringCollection();

                        string datatype = "System.String";

                        switch (attribute.SchemaTypeName.Name)
                        {
                            //xs datatypes...
                            case "string":
                                datatype = "System.String";
                                flags = flags | XsdAttributeFlags.TrimBlanks;
                                break;
                            case "Name":
                                datatype = "System.String";
                                flags = flags | XsdAttributeFlags.NoWhiteSpace | XsdAttributeFlags.TrimBlanks;
                                break;
                            case "int":
                                datatype = "System.Int32";
                                break;
                            case "boolean":
                                datatype = "System.Boolean";
                                break;
                            case "float":
                            case "double":
                                datatype = "System.Double";
                                break;

                            //Specific Datatypes for adjusting the editor, derived from string...
                            case "stringlist":
                                datatype = typeof(System.Collections.Specialized.StringCollection).AssemblyQualifiedName;
                                editor = XsdAttributeEditor.ArrayofString;
                                break;
                            case "text":
                                datatype = "System.String";
                                editor = XsdAttributeEditor.MultiLine;
                                flags = flags | XsdAttributeFlags.TrimBlanks;
                                break;
                            case "filename":
                                datatype = "System.String";
                                editor = XsdAttributeEditor.Filename;
                                break;
                            case "html":
                                datatype = "EwbDesigner.NET.XmlCacheItem";
                                editor = XsdAttributeEditor.Html;
                                break;

                            default:
                                if (attribute.SchemaType != null)
                                {
                                    if (attribute.SchemaType.Content is XmlSchemaSimpleTypeRestriction)
                                    {
                                        datatype = "EwbDesigner.NET.XmlCacheItem";
                                        editor = XsdAttributeEditor.Enumeration;

                                        foreach (XmlSchemaEnumerationFacet facet in ((XmlSchemaSimpleTypeRestriction)(attribute.SchemaType.Content)).Facets)
                                        {
                                            values.Add(facet.Value);
                                        }
                                    }
                                }
                                else
                                {
                                    Debug.Print("Error: Unsupported Type '{0}' in '{1} ({2})'", attribute.SchemaTypeName.Name, elem.Name);
                                }
                                break;
                        }

                        String value = "";


                        //Handle Fixed attribute and (Default) Values.
                        if (attribute.FixedValue != null)
                        {
                            value = attribute.FixedValue;
                            flags = flags | XsdAttributeFlags.FixedValue;
                        }
                        else
                        {
                            if (attribute.DefaultValue != null)
                            {
                                value = attribute.DefaultValue;
                            }
                        }

                        String annotation = null;
                        if (attribute.Annotation != null)
                        {
                            foreach (XmlSchemaObject xso in attribute.Annotation.Items)
                            {
                                if (xso is XmlSchemaDocumentation)
                                {
                                    foreach (XmlNode xn in ((XmlSchemaDocumentation)xso).Markup)
                                    {
                                        //Debug.WriteLine(xn.InnerText);

                                        annotation += xn.InnerText;
                                    }
                                }
                            }
                        }
                        attributes.Add(attribute.Name, new XsdAttributeItem(attribute.Name, value, datatype, flags, values, editor, annotation));

                        //Trace.WriteLine("Adding Attribute '" + attribute.Name + "'", "Trace");
                    }
                }

                //TODO Why this check not just when it's a XmlSchemaElement?
                if (elem.RefName.IsEmpty)
                {
                    String key = "";

                    //Build Key...
                    if (path == "")
                    {
                        key = elem.Name;
                    }
                    else
                    {
                        key = path + System.IO.Path.DirectorySeparatorChar + elem.Name;
                    }

                    //NOTE XmlSchemaChoice.MinOccurs is neglected as we do not know which one to add...

                    //Add Key to Dictionary...
                    XsdCacheItem o = new XsdCacheItem(
                        elem.Name,
                        elem.MinOccurs,
                        elem.MaxOccurs,
                        elem.GetHashCode(),
                        elem.Parent is XmlSchemaChoice ? ((XmlSchemaChoice)elem.Parent).MaxOccurs : 0,
                        attributes);

                    //BUG, first file we enter the if below but no root node is shown.
                    //     subsequent loads we skipp the if below but show the root node.

                    XsdTreeNode newnode = null;
                    if (Data.XsdModel.Root.Value == null)
                    {
                        Data.XsdModel.Root.Value = new XsdCacheItem("Root", 1, 1, 0, 1, new XsdAttributes());
                        node = Data.XsdModel.Root;
                        //Data.XsdModel.Root.Value = o;
                        //newnode = Data.XsdModel.Root;
                    }
                    //else
                    {
                        newnode = (XsdTreeNode)node.Children.Add(o);
                    }

                    //Add Annotation if any and clear it.
                    if (Annotation != "")
                    {
                        newnode.Value.annotation = Annotation;
                        Annotation = "";
                    }

                    //Debug.WriteLine("Added: " + newnode.Path);

                    if (complexType != null && complexType.Name == null)
                    {
                        DoTraverseParticle(
                            complexType.ContentTypeParticle,
                            key,
                            newnode);
                    }
                }
            }
            else if (particle is XmlSchemaGroupBase)
            {
                //xs:all,
                //xs:choice,
                //xs:sequence
                XmlSchemaGroupBase baseParticle = particle as XmlSchemaGroupBase;
                foreach (XmlSchemaParticle subParticle in baseParticle.Items)
                {
                    DoTraverseParticle(
                        subParticle,
                        path,
                        node);
                }
            }
            Debug.Unindent();
        }

        /// <summary>
        /// Parse the XSD Schema.
        /// </summary>
        /// <param name="xs">The Schema to parse.</param>
        private void DoTraverseSchema(XmlSchema xs)
        {
            //XmlSchemaComplexType complexType;
            //
            //Trace.WriteLine("Parsing complex types...", "Trace");
            //
            //TODO: Does this do anything that gets added at all?
            //foreach (XmlSchemaType type in xs.SchemaTypes.Values)
            //{
            //    complexType = type as XmlSchemaComplexType;
            //    if (complexType != null)
            //    {
            //        DoTraverseParticle(complexType.ContentTypeParticle, "", Data.XsdModel.Root);
            //    }
            //}

            //Trace.WriteLine("Parsing elements...", "Trace");
            foreach (XmlSchemaElement el in xs.Elements.Values)
            {
                DoTraverseParticle(el, "", Data.XsdModel);
            }
        }

        private void ModelDelegates()
        {
            /////////////////////////

            // Can the given object be expanded?
            Structure.CanExpandGetter = delegate(Object x)
            {
                if (x is XmlTreeNode)
                {
                    //Console.WriteLine("{0} - {1}", ((XmlTreeNode)x).Value.name, ((XmlTreeNode)x).Children.Count);
                    return ((XmlTreeNode)x).Children.Count != 0;
                }
                return false;
            };

            // What objects should belong underneath the given model object?
            Structure.ChildrenGetter = delegate(Object x)
            {
                if (x is XmlTreeNode)
                {
                    //foreach (XmlTreeNode xmlnode in ((XmlTreeNode)x).Children)
                    //{
                    //    Console.WriteLine("{0}", xmlnode.Path);
                    //}
                    //Console.WriteLine("^-------");
                    return ((XmlTreeNode)x).Children;
                }

                throw new ArgumentException("Should be XmlTreeNode");
            };

            /////////////////////////
            //0: Title
            /////////////////////////

            //olvColumn1.Text = "titel";
            olvColumn1.AspectGetter = delegate(object x)
            {
                return x;
            };

            olvColumn1.AspectToStringConverter = delegate(object x)
            {
                if (x is XmlTreeNode)
                {
                    if (((XmlTreeNode)x).Value.attributes.ContainsKey("titel"))
                    {
                        if (((XmlTreeNode)x).Value.attributes["titel"].value.Trim() == "")
                        {
                            return "[" + ((XmlTreeNode)x).Value.name + "]";
                        }
                        else
                        {
                            return ((XmlTreeNode)x).Value.attributes["titel"].value;
                        }
                    }
                    else
                    {
                        return "[" + ((XmlTreeNode)x).Value.name + "]";
                    }
                }
                else
                {
                    return "error";
                }
            };

            /////////////////////////
            //1: Type
            /////////////////////////

            //olvColumn2.Text = "type";

            olvColumn2.AspectGetter = delegate(object x)
            {
                return x;
            };

            olvColumn2.AspectToStringConverter = delegate(object x)
            {
                if (x is XmlTreeNode)
                {
                    return ((XmlTreeNode)x).Value.name;
                }

                throw new ArgumentException("Should be XmlTreeNode");
            };

            /////////////////////////
            //2: Multiplicity
            /////////////////////////

            //olvColumn3.Text = "multipliciteit";

            olvColumn3.AspectGetter = delegate(object x)
            {
                return x;
            };

            olvColumn3.AspectToStringConverter = delegate(object x)
            {
                if (x is XmlTreeNode)
                {
                    XsdCacheItem xsd = Data.XsdModel.NodeByPath(((XmlTreeNode)x).Path).Value;

                    //Max=-1 gives infinity.
                    if (xsd.max == Decimal.MaxValue)
                    {
                        return String.Format("{0}..{1}", xsd.min.ToString(), " ∞ ");
                    }
                    else if (xsd.min == xsd.max)
                    {
                        return String.Format("{0}", xsd.min.ToString());
                    }
                    else
                    {
                        return String.Format("{0}..{1}", xsd.min.ToString(), xsd.max.ToString());
                    }
                }
                else
                {
                    return "error";
                }
            };

            /////////////////////////
            //3: Optional?
            /////////////////////////

            //olvColumn4.Text = "optioneel?";

            olvColumn4.AspectGetter = delegate(object x)
            {
                return x;
            };

            olvColumn4.AspectGetter = delegate(object x)
            {
                if (x is XmlTreeNode)
                {
                    XsdCacheItem xsd = Data.XsdModel.NodeByPath(((XmlTreeNode)x).Path).Value;

                    switch (xsd.min == 0)
                    {
                        case true: return true;
                        default:
                            return false;
                    }
                }
                else
                {
                    return CheckState.Indeterminate;
                }
            };

            /////////////////////////
        }

        private void ProcessModel(string value)
        {
            fModel = value;

            Collection = null;

            //TODO Targetnamespace should be picked up from xsd...

            string nsTarget = "pimv2";

            Data.XsdModel.Clear();
            Data.XsdModel.Root.Value = null;

            Data.XmlModel.Clear();
            Data.XmlModel.Root.Value = null;

            StructureForm.Instance.ClearnPopupMenu();

            XmlReaderSettings xsdSettings = new XmlReaderSettings();
            XmlSchema schema = xsdSettings.Schemas.Add(nsTarget, new XmlTextReader(Model));
            xsdSettings.ValidationType = ValidationType.Schema;
            xsdSettings.ValidationEventHandler += new ValidationEventHandler(OnValidationCallback);
            xsdSettings.Schemas.Compile();

            if (schema.IsCompiled)
            {
                DoTraverseSchema(schema);

                //Build the Add MenuItem'key SubMenu.
                foreach (XsdTreeNode xsdnode in Data.XsdModel)
                {
                    Instance.DoAddMenuItem(xsdnode);
                }

                //Add RootNodes (and its mandatory offspring by recursion).
                foreach (XsdTreeNode xsdnode in Data.XsdModel)
                {
                    if (xsdnode == Data.XsdModel.Root)
                    {
                        //Root does not exist here...
                        DoAddXmlModelNodes(xsdnode, null);
                    }
                }

                Collection = Data.XmlModel.Children;

                Structure.ExpandAll();
            }
        }

        #endregion Methods

    }
}